import 'dart:io';

import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:flutter_keep_book/core/utils/m_logs.dart';


import 'request_interceptor.dart';
import 'response_interceptor.dart';
import 'result.dart';

enum HttpMethod { get, post }

/// 网络请求工具。
class HttpManager {
  /// 网络请求工具。
  ///
  /// [baseUrl] 域名。如：https://api.xxx.com。
  ///
  /// [signingKey] 签名密钥。
  ///
  /// [connectTimeout] 链接超时时长，默认为 15s。
  ///
  /// [headersProvider] 提供一个 [Map] 用于填充 Request Header。
  ///
  /// [extraParamsProvider] 所有接口都额外添加的公共参数。如：App 版本号、手机型号、
  /// 手机系统版本号、时间戳等等。
  HttpManager({
    this.baseUrl = '',
    this.signingKey,
    Duration connectTimeout = const Duration(seconds: 15),
    MapProvider? headersProvider,
    this.extraParamsProvider,
  }) {
    _dio = Dio(BaseOptions(
      baseUrl: baseUrl,
      connectTimeout: connectTimeout,
    ));
    _dio.interceptors.add(RequestInterceptor(
      headersProvider: headersProvider ?? () => headers ?? {},
    ));
    _dio.interceptors.add(ResponseInterceptor());
  }

  /// 域名。
  final String baseUrl;

  /// 签名密钥。
  ///
  /// 若不为空，则使用 params 生成一个签名并以 sign 为 key 添加到 params 中。
  ///
  /// 签名生成规则请查看方法 [generateSign]。
  final String? signingKey;

  /// 所有接口都额外添加的公共参数。如：App 版本号、手机型号、手机系统版本号、时间戳等等。
  final FutureMapProvider? extraParamsProvider;

  /// 与 [headersProvider] 作用一样。[headers] 用于子类继承时重载。
  /// 两者都有值时，优先使用 [headersProvider]。
  Map<String, dynamic>? get headers => null;

  /// 与 [extraParamsProvider] 作用一样。[extraParams] 用于子类继承时重载。
  /// 两者都有值时，优先使用 [extraParamsProvider]。
  Future<Map<String, dynamic>>? get extraParams => null;

  late Dio _dio;

  /// 设置/更新代理。参数格式：{"host":"192.168.1.1","port":"8888"}
  void setProxy(Map<String, String>? networkProxy) {
    final adapter = _dio.httpClientAdapter;

    if (adapter is! IOHttpClientAdapter) return;

    final host = networkProxy?['host'] ?? '';
    final port = networkProxy?['port'] ?? '';

    if (host.isEmpty || port.isEmpty) {
      adapter.createHttpClient = null;
    } else {
      adapter.createHttpClient = () {
        final client = HttpClient();
        client.findProxy = (uri) => 'PROXY $host:$port';
        client.badCertificateCallback = (cert, host, port) => false;
        return client;
      };
    }
  }

  /// 网络请求。
  ///
  /// [baseUrl] 域名。如果本次请求需要使用其他域名则传入值，否则会使用创建 [HttpManager]
  /// 实例时提供的 [baseUrl]。
  ///
  /// [path] 路径。如：/api/user/info。
  ///
  /// [params] 参数。如果创建 [HttpManager] 时传入了 [signingKey]，那么会使用 [params]
  /// 和 [extraParams] 来生成一个签名，然后将该签名以 sign 为 key 添加到请求参数中。
  Future<NetworkResult> request({
    String? baseUrl,
    required String path,
    HttpMethod method = HttpMethod.get,
    Map<String, dynamic>? params,
  }) async {
    final queryParameters = <String, dynamic>{};

    if (params != null && params.isNotEmpty) {
      queryParameters.addAll(params);
    }

    final provider = extraParamsProvider?.call() ?? extraParams;
    if (provider != null) {
      final extraParams = await provider;
      if (extraParams.isNotEmpty) {
        queryParameters.addAll(extraParams);
      }
    }
/*
    // 生成签名
    final key = signingKey ?? '';
    if (queryParameters.isNotEmpty && key.isNotEmpty) {
      queryParameters['sign'] = generateSign(
        key: key,
        parameters: queryParameters,
      );
    }
*/
    if (baseUrl == null || baseUrl.isEmpty) {
      if (_dio.options.baseUrl != this.baseUrl) {
        _dio.options.baseUrl = this.baseUrl;
      }
    } else {
      _dio.options.baseUrl = baseUrl;
    }

    MLogs.info("---------->网络请求链接$path");
    MLogs.info("---------->网络请求参数${queryParameters.toString()}");

    NetworkResult result;
    try {
      Response? response;

      switch (method) {
        case HttpMethod.get:
          response = await _dio.get(
            path,
            queryParameters: queryParameters.isEmpty ? null : queryParameters,
          );
          break;
        case HttpMethod.post:
          response = await _dio.post(
            path,
            data: queryParameters.isEmpty ? null : queryParameters,
          );
          break;
      }

      final tmpData = response.data;
      if (tmpData != null && tmpData is NetworkResult) {
        result = tmpData;
      } else {
        result = Result.failedWithMsg('网络错误');
      }
    } on DioException catch (e) {
      MLogs.info("---------->网络请求结果异常-$path---$e");
      result = Result.failedWithMsg((e.message ?? "网络错误"));
    }
    MLogs.info("---------->网络请求结果$path---$result");
    return result;
  }

  /// GET 请求。
  Future<NetworkResult> requestGet({
    String? baseUrl,
    required String path,
    Map<String, dynamic>? params,
  }) =>
      request(
        baseUrl: baseUrl,
        path: path,
        method: HttpMethod.get,
        params: params,
      );

  /// POST 请求。
  Future<NetworkResult> requestPost({
    String? baseUrl,
    required String path,
    Map<String, dynamic>? params,
  }) =>
      request(
        baseUrl: baseUrl,
        path: path,
        method: HttpMethod.post,
        params: params,
      );
}
